﻿using System;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.IO;
using System.Collections;
using System.Diagnostics;

namespace org.syncsoft.overalt.gui.socket
{
    /// <summary>
    /// Classe utilizado para realizar a comunicação entre .net e java (via socket)
    /// </summary>
    public class ClientSocket
    {

        #region variables
        /// <summary>
        /// Um delegate AsyncCallback é usado para Processar os resultados de uma operação assíncrona em um thread separado. 
        /// O delegate AsyncCallback representa um método de retorno de chamada que é chamado quando a operação assíncrona for concluída. 
        /// O método de retorno de chamada usa um parâmetro de IAsyncResult, que é usado subseqüentemente para obter os resultados da 
        /// operação assíncrona.
        /// </summary>
        private AsyncCallback WorkerCallBack;
        private Socket mainSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        private IPEndPoint ipLocal;
        private byte[] c_DataBuffer = new byte[10000000];
        Queue msgQueue = new Queue();
        private int m_Port = 0;
        private byte[] m_ReceivedBytes;
        private string m_ReceivedText = "";
        private string m_SentText = "";
        #endregion

        #region constructor
        /// <summary>
        /// Socket cliente
        /// </summary>
        /// <param name="IP">IP local</param>
        /// <param name="port">Porta do socket servidor</param>
        public ClientSocket(string IP, int port)
        {
            try
            {
                m_Port = port;
                IPAddress ipAddress = IPAddress.Parse(IP);
                ipLocal = new IPEndPoint(ipAddress, port);
            }
            catch (Exception ex)
            {
                throw new Exception("Error! " + ex.Message);
            }
        }
        #endregion

        #region methods
        /// <summary>
        /// Port for connection whit ServerSocket
        /// </summary>
        public int Port
        {
            get
            {
                return (m_Port);
            }
        }

        /// <summary>
        /// This method is never used
        /// </summary>
        public byte[] ReceivedBytes
        {
            get
            {
                byte[] temp = null;
                if (m_ReceivedBytes != null)
                {
                    temp = m_ReceivedBytes;
                    m_ReceivedBytes = null;
                }
                return (temp);
            }
        }

        /// <summary>
        /// Message from serversocket
        /// </summary>
        public string ReceivedText
        {
            get
            {
                string temp = m_ReceivedText;
                m_ReceivedText = "";
                return (temp);
            }
        }

        /// <summary>
        /// Messagem enviada pelo Socket
        /// </summary>
        public string WriteText
        {
            get
            {
                string temp = m_SentText;
                m_SentText = "";
                return (temp);
            }
        }

        /// <summary>
        /// Envia uma mensagem de texto pela conexão
        /// </summary>
        /// <param name="mens">Mensagem para ser enviada</param>
        public bool SendText(string mens)
        {
            try
            {
                msgQueue.Enqueue(mens);
                while (true)
                {
                    if (this.Connected && msgQueue.Count > 0)
                    {

                        byte[] byData = System.Text.Encoding.ASCII.GetBytes(msgQueue.Dequeue().ToString());
                        int NumBytes = mainSocket.Send(byData);

                        if (NumBytes == byData.Length)
                        {
                            return true;
                        }
                        else
                            return false;
                    }
                    else
                    {
                        return false;
                        break;
                    }
                }
            }
            catch (Exception ex)
            {
                throw new Exception("Não foi possível enviar as informações!");
                return false;
            }
        }

        /// <summary>
        /// Envia arquivo para a conexão selecionada no índice
        /// </summary>
        /// <param name="FileName">Arquivo a ser enviado</param>
        public bool SendFile(string FileName)
        {
            try
            {
                mainSocket.SendFile(FileName);
                return true;
            }
            catch (Exception ex)
            {
                throw new Exception("Não foi possível enviar o arquivo!");
                return false;
            }
        }

        /// <summary>
        /// Envia arquivo
        /// </summary>
        /// <param name="FileName">Arquivo a ser enviado</param>
        /// <param name="PreString">String enviada antes do arquivo</param>
        /// <param name="PosString">String enviada logo depois do arquivo</param>
        public bool SendFile(string FileName, string PreString, string PosString)
        {
            try
            {
                byte[] preBuf = Encoding.ASCII.GetBytes(PreString);
                byte[] postBuf = Encoding.ASCII.GetBytes(PosString);
                mainSocket.BeginSendFile(FileName, preBuf, postBuf, 0, new AsyncCallback(FileSendCallback), mainSocket);
                return true;
            }
            catch (ArgumentException se)
            {
                return false;
            }
            catch (ObjectDisposedException se)
            {
                return false;
            }
            catch (SocketException se)
            {
                return false;
            }
        }

        /// <summary>
        /// Retorna true se o ClientSocket estiver conectado a um Servidor
        /// </summary>
        public bool Connected
        {
            get
            {
                return (mainSocket.Connected);
            }
        }
        #endregion

        #region functions and events
        /// <summary>
        /// Conecta-se ao IP e Porta configurados
        /// </summary>
        public bool Connect()
        {
            try
            {
                //Connect to the server
               
               mainSocket.BeginConnect(ipLocal, new AsyncCallback(WaitConfirmConnect), null);
                return true;
            }
            catch (Exception ex)
            {
                throw new Exception("Falha na conexão. " + ex.Message);
                return false;
            }
        }

        //Aguarda a confirmação de conexão
        private void WaitConfirmConnect(IAsyncResult asyn)
        {
            WaitForData(mainSocket);
        }

        /// <summary>
        /// Espera por dados
        /// </summary>
        /// <param name="socket">Socket</param>
        private void WaitForData(Socket socket)
        {
            SocketError error;
            try
            {
                if (WorkerCallBack == null)
                {
                    WorkerCallBack = new AsyncCallback(OnDataReceived);
                }

                //O socket recebe dados de forma assíncrona
                socket.BeginReceive(c_DataBuffer, 0, c_DataBuffer.Length, SocketFlags.None, out error, WorkerCallBack, null);

            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ar"></param>
        private void FileSendCallback(IAsyncResult ar)
        {
            Socket workerSocket = (Socket)ar.AsyncState;
            workerSocket.EndSendFile(ar);

        }

        /// <summary>
        /// Evento para recebimento de dados
        /// </summary>
        /// <param name="asyn"></param>
        private void OnDataReceived(IAsyncResult asyn)
        {
            try
            {
                //Recebe dados
                int bytes = mainSocket.EndReceive(asyn);
                if (bytes < 1)
                {
                    Disconnect();
                }
                else
                {
                    //m_ReceivedBytes = new byte[c_DataBuffer.Length + 1];
                    //m_ReceivedBytes = c_DataBuffer;

                    char[] chars = new char[bytes + 1];
                    //Cria decodificador
                    Decoder decoder = Encoding.UTF8.GetDecoder();
                    //Decodifica os bytes em caractesres
                    decoder.GetChars(c_DataBuffer, 0, bytes, chars, 0);
                    //Converte para string
                    m_ReceivedText = new String(chars);

                    WaitForData(mainSocket);
                }
            }
            catch (Exception ex)
            {
                Process.GetCurrentProcess().Kill();
                throw new Exception("Falha ao receber dados!");
                 //ExitTime.AddMilliseconds(30);
            }
        }

        public void DisconnectMain()
        {
            mainSocket.Disconnect(true);
        }
        /// <summary>
        /// Desfaz a conexão com o Servidor
        /// </summary>
        public bool Disconnect()
        {
            //mainSocket.Disconnect(false);
            mainSocket.Close();
            if (!mainSocket.Connected)
            {
                return true;
            }
            else
                return false;
        }
        #endregion
    }

    /// <summary>
    /// Exception ClientSocketException
    /// </summary>
    public class ClientSocketException : Exception
    {
        public ClientSocketException(string msg)
        {

        }
    }
}

